/**
 * @file Implements ObjectContainer.
 * @author Zmei Zelen <zmei.zelen@gmail.com>
 */



/**
 * A convenient object container.
 * 
 * @constructor
 * @returns {ObjectContainer}
 */
function ObjectContainer () {
        this.objects = new Array();
        this.names = new Object();
}



/**
 * Adds object to the container.
 * 
 * @param {object} object The object that will be contained.
 * @param {string} name An optional name for the object.
 * @returns {object} The object.
 */
ObjectContainer.prototype.add = function (object, name) {
    var id = this.objects.push(object) - 1;
    if (name) this.names[name] = id;    
    return object;
};



/**
 * Return the index of given reference object.
 * 
 * @param {number|string|object} target Object, id or objects's name. If id is passed, then will test if it exists.
 * @returns {number} The id of the object and -1 if no such objects is found or id an id is out of range.
 */
ObjectContainer.prototype.indexOf = function (target) {
    var id;
    
    if (typeof target === 'number') {         
        id = target >= this.objects.length ? -1 : target;
        
    } else if (typeof target === 'string') {
        if (target in this.names) {
            id = this.names[target];
        } else id = -1;
        
    } else {
        id = this.objects.indexOf(target);
    }
    
    return id;
};



/**
 * Return the name of an object in the container or undefined if it isn't named or found.
 * 
 * @param {number|object} target The object or it's id.
 * @returns {string|undefined} Object's name or undefined.
 */
ObjectContainer.prototype.nameOf = function (target) {
    var id = this.indexOf(target);
    
    if (id !== -1) {
        for (var name in this.names) {
            if (this.names[name] === id) return name;
        }
    }
    
    return undefined;
};



/**
 * Insert object in the container.
 * 
 * @param {number|string|object} reference Reference object, id or name.
 * @param {object} object The object that will be inserted.
 * @param {string} name Optional name for the object.
 * @param {boolean} after Pass true to insert after the reference object, or false to insert before the reference object.
 * @returns {object|undefined} The object or undefined if the reference object is not found.
 */
ObjectContainer.prototype.insert = function (reference, object, name, after) {    
    var id = this.indexOf(reference);
    
    if (id !== -1) {    
        id = after ? id + 1 : id;        
        this.objects.splice(id, 0, object);
        if (name) this.names[name] = id;
        return object;
        
    } else return undefined; 
};



/**
 * Remove object from the container.
 * 
 * @param {number|string|object} target Object, id or name.
 * @returns {undefined}
 */
ObjectContainer.prototype.remove = function (target) {
    var id = this.indexOf(target);
    if (id !== -1) {
        this.objects[id] = null;
        for (var name in this.names) {
            if (this.names[name] === id) delete this.names[name];
        }
    }    
};



/**
 * Invokes a method of every object in the container. All arguments after 'method' are passed to the call.
 * 
 * @param {string} method
 * @returns {array} Array of call results.
 */
ObjectContainer.prototype.invoke = function (method /* , ... */) {
    var results = new Array(this.objects.length),
        _arguments = Array.prototype.slice.call(arguments);
    _arguments.shift();
    
    for (var id = 0; id < this.objects.length; id++) {
        if (this.objects[id] && typeof this.objects[id][method] === 'function') {                        
            results[id] = this.objects[id][method].apply(this.objects[id], _arguments);                        
        }
    }
    return results;
};



/**
 * Calls every object (if it is a function) in the container.
 * 
 * @returns {array} Array of call results.
 */
ObjectContainer.prototype.call = function () {
    var results = new Array(this.objects.length);
    for (var id = 0; id < this.objects.length; id++) {
        if (typeof this.objects[id] === 'function') {            
            results[id] = this.objects[id].apply(this.objects[id], arguments);
        }
    }
    return results;
};


/**
 * Calls every object (if it is a function) in the container.
 * 
 * @param {Array} _arguments Arguments to pass to the function.
 * @returns {Array} Array of call results.
 */
ObjectContainer.prototype.apply = function (_arguments) {
    var results = new Array(this.objects.length);
    for (var id = 0; id < this.objects.length; id++) {       
        if (typeof this.objects[id] === 'function') {            
            results[id] = this.objects[id].apply(this.objects[id], _arguments);
        }
    }
    return results;
};


/**
 * Applies a function(object[, id[, name]]) over every contained object. 
 * 
 * @param {function} procedure The function to apply.
 * @param {boolean|undefined} check If true will halt the execution when the procedure returns false.
 * @returns {boolean} True on success, false on halt.
 */
ObjectContainer.prototype.each = function (procedure, check) {
    for (var id = 0; id < this.objects.length; id++) {       
        if (this.objects[id]) {            
            if (!procedure(this.objects[id], id, this.nameOf(id))) {
                if (check) return false;
            }
        }
    }
    return true;
};


/**
 * Returns object from the container by specified id or name.
 * 
 * @param {number|string} target Object's id or name.
 * @returns {object} The object, or null if it's not found.
 */
ObjectContainer.prototype.get = function (target) {
    var id = this.indexOf(target);
    
    if (id !== -1) {
        return this.objects[id];
    } else {
        return undefined;
    }
};



/**
 * Removes all objects.
 * 
 * @returns {undefined}
 */
ObjectContainer.prototype.empty = function () {
    for (var id = 0; id < this.objects.length; id++) {
        this.remove(id);
    }
};